﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using MultiViewer.Display;

using System.Diagnostics;
using System.Configuration;
using MultiViewer.Network;
using System.Threading;
using MultiViewer.Display.TiledImages;

namespace MultiViewer.Master
{
    /// <summary>Interaction logic for MultiViewer Master main UI</summary>
    public partial class MainWindow : MultiViewerWindow
    {
        int xMon;
        int yMon;
        private bool[] MouseVisible;
        private MasterConfigurationSection config;

        public MainWindow()
        {
            MouseVisible = new bool[xMon * yMon];
            InitializeComponent();
#if DEBUG
            Trace.Listeners.Add(traceViewer.Listener);
#else
            Debug.Listeners.Add(traceViewer.Listener);
#endif

            Trace.WriteLine("MultiViewer.Master is starting...");
            Debug.WriteLine("MultiViewer is built in Debug mode.");

            ThreadPool.SetMaxThreads(32, 32);
        }

        protected override void OnPreviewMouseMove(MouseEventArgs e)
        {
            xMon = displayGrid.Config.Width;
            yMon = displayGrid.Config.Height;
            if (Node.ConnectedNodes.Length != 1)
            {
                if (MouseVisible.Length == 0) MouseVisible = new bool[xMon * yMon];
                int Cursor_w;
                int Cursor_h;
                if (!config.Display.Glass)
                {
                Cursor_w = config.Display.CursorWidth;// 16;
                Cursor_h = config.Display.CursorHeight;// 16;
                }
                else
                {
                    Cursor_w = config.Display.GlassWidth;// 16;
                    Cursor_h = config.Display.GlassHeight;// 16;
                }
                MultiViewer.Master.MainWindow main = (MultiViewer.Master.MainWindow)Application.Current.MainWindow;
                int res_X = displayGrid.Config.Display.Width;
                int res_Y = displayGrid.Config.Display.Height;
                double cw = ((double)xMon * res_X) / ((int)main.displayGrid.ActualWidth);
                double ch = ((double)yMon * res_Y) / ((int)main.displayGrid.ActualHeight);
                try
                {
                    double Mouse_x = e.MouseDevice.GetPosition(this).X;
                    double Mouse_y = e.MouseDevice.GetPosition(this).Y;
                    double Mouse_x_Temp = Mouse_x;
                    double Mouse_y_Temp = Mouse_y;
                    if (config.Display.Glass)
                    {
                        Mouse_x_Temp -= (config.Display.GlassWidth - 2) / (cw) / 2;
                        Mouse_y_Temp -= (config.Display.GlassHeight - 2) / (ch) / 2;

                    }
                    for (int j = 0; j < yMon; j++)
                    {
                        for (int i = 0; i < xMon; i++)
                        {
                            if ((Mouse_x_Temp >= i * main.displayGrid.ActualWidth / xMon - Cursor_w / cw) &&
                                (Mouse_x_Temp <= (i + 1) * main.displayGrid.ActualWidth / xMon) &&
                                (Mouse_y_Temp >= j * main.displayGrid.ActualHeight / yMon - Cursor_h / ch) &&
                                (Mouse_y_Temp <= (j + 1) * main.displayGrid.ActualHeight / yMon))
                            {
                                int[] nodes = new int[1];
                                nodes[0] = Node.ClientNodes[xMon * j + i];
                                Node.Notify(nodes, new MouseNotification { Position = new Point(Mouse_x * cw - (res_X * i), Mouse_y * ch - (res_Y * j)) });
                                MouseVisible[xMon * j + i] = true;
                            }
                            else
                            {
                                if (MouseVisible[xMon * j + i])
                                {
                                    int[] nodes = new int[1];
                                    nodes[0] = Node.ClientNodes[xMon * j + i];
                                    Node.Notify(nodes, new MouseNotification { Position = new Point(-10000, -10000) });
                                    MouseVisible[xMon * j + i] = false;
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
            //if(Node != null)
            //    Node.Notify(Node.ClientNodes, new MouseNotification { Position = e.GetPosition(this) });
            base.OnPreviewMouseMove(e);
        }
        protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            try
            {
                if (Node.ConnectedNodes.Length != 1 && config.Display.SimulateMouseEvents == true)
                {
                    MultiViewer.Master.MainWindow main = (MultiViewer.Master.MainWindow)Application.Current.MainWindow;
                    int res_X = displayGrid.Config.Display.Width;
                    int res_Y = displayGrid.Config.Display.Height;
                    xMon = displayGrid.Config.Width;
                    yMon = displayGrid.Config.Height;
                    double cw = ((double)xMon * res_X) / ((int)main.displayGrid.ActualWidth);
                    double ch = ((double)yMon * res_Y) / ((int)main.displayGrid.ActualHeight);
                    try
                    {
                        double Mouse_x = e.MouseDevice.GetPosition(this).X;
                        double Mouse_y = e.MouseDevice.GetPosition(this).Y;
                        for (int j = 0; j < yMon; j++)
                        {
                            for (int i = 0; i < xMon; i++)
                            {
                                if ((Mouse_x >= i * main.displayGrid.ActualWidth / xMon) &&
                                    (Mouse_x <= (i + 1) * main.displayGrid.ActualWidth / xMon) &&
                                    (Mouse_y >= j * main.displayGrid.ActualHeight / yMon) &&
                                    (Mouse_y <= (j + 1) * main.displayGrid.ActualHeight / yMon))
                                {
                                    //Show cursor in the left upper window
                                    int[] nodes = new int[1];
                                    nodes[0] = Node.ClientNodes[xMon * j + i];
                                    Node.Notify(nodes, new MouseClickNotification { Button = MultiViewer.Display.MouseButton.LeftUp, Position = new Point(Mouse_x * cw - (res_X * i), Mouse_y * ch - (res_Y * j)) });
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }
            }
            catch (Exception exc)
            {
                MessageBox.Show("Cannot load mouse configuration: " + exc.Message);
                Close();
                return;
            }
            base.OnPreviewMouseLeftButtonUp(e);
        }


        protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            try
            {
                if (Node.ConnectedNodes.Length != 1 && config.Display.SimulateMouseEvents == true)
                {
                    MultiViewer.Master.MainWindow main = (MultiViewer.Master.MainWindow)Application.Current.MainWindow;
                    int res_X = displayGrid.Config.Display.Width;
                    int res_Y = displayGrid.Config.Display.Height;
                    xMon = displayGrid.Config.Width;
                    yMon = displayGrid.Config.Height;
                    double cw = ((double)xMon * res_X) / ((int)main.displayGrid.ActualWidth);
                    double ch = ((double)yMon * res_Y) / ((int)main.displayGrid.ActualHeight);
                    try
                    {
                        double Mouse_x = e.MouseDevice.GetPosition(this).X;
                        double Mouse_y = e.MouseDevice.GetPosition(this).Y;
                        for (int j = 0; j < yMon; j++)
                        {
                            for (int i = 0; i < xMon; i++)
                            {
                                if ((Mouse_x >= i * main.displayGrid.ActualWidth / xMon) &&
                                    (Mouse_x <= (i + 1) * main.displayGrid.ActualWidth / xMon) &&
                                    (Mouse_y >= j * main.displayGrid.ActualHeight / yMon) &&
                                    (Mouse_y <= (j + 1) * main.displayGrid.ActualHeight / yMon))
                                {
                                    //Show cursor in the left upper window
                                    int[] nodes = new int[1];
                                    nodes[0] = Node.ClientNodes[xMon * j + i];
                                    Node.Notify(nodes, new MouseClickNotification { Button = MultiViewer.Display.MouseButton.LeftDown, Position = new Point(Mouse_x * cw - (res_X * i), Mouse_y * ch - (res_Y * j)) });
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }
            }
            catch (Exception exc)
            {
                MessageBox.Show("Cannot load mouse configuration: " + exc.Message);
                Close();
                return;
            }
            base.OnPreviewMouseLeftButtonDown(e);
        }

        protected override void OnPreviewMouseRightButtonUp(MouseButtonEventArgs e)
        {
            try
            {
                if (Node.ConnectedNodes.Length != 1 && config.Display.SimulateMouseEvents == true)
                {
                    MultiViewer.Master.MainWindow main = (MultiViewer.Master.MainWindow)Application.Current.MainWindow;
                    int res_X = displayGrid.Config.Display.Width;
                    int res_Y = displayGrid.Config.Display.Height;
                    xMon = displayGrid.Config.Width;
                    yMon = displayGrid.Config.Height;
                    double cw = ((double)xMon * res_X) / ((int)main.displayGrid.ActualWidth);
                    double ch = ((double)yMon * res_Y) / ((int)main.displayGrid.ActualHeight);
                    try
                    {
                        double Mouse_x = e.MouseDevice.GetPosition(this).X;
                        double Mouse_y = e.MouseDevice.GetPosition(this).Y;
                        for (int j = 0; j < yMon; j++)
                        {
                            for (int i = 0; i < xMon; i++)
                            {
                                if ((Mouse_x >= i * main.displayGrid.ActualWidth / xMon) &&
                                    (Mouse_x <= (i + 1) * main.displayGrid.ActualWidth / xMon) &&
                                    (Mouse_y >= j * main.displayGrid.ActualHeight / yMon) &&
                                    (Mouse_y <= (j + 1) * main.displayGrid.ActualHeight / yMon))
                                {
                                    //Show cursor in the left upper window
                                    int[] nodes = new int[1];
                                    nodes[0] = Node.ClientNodes[xMon * j + i];
                                    Node.Notify(nodes, new MouseClickNotification { Button = MultiViewer.Display.MouseButton.RightUp, Position = new Point(Mouse_x * cw - (res_X * i), Mouse_y * ch - (res_Y * j)) });
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }
            }
            catch (Exception exc)
            {
                MessageBox.Show("Cannot load mouse configuration: " + exc.Message);
                Close();
                return;
            }
            base.OnPreviewMouseRightButtonUp(e);
        }

        protected override void OnPreviewMouseRightButtonDown(MouseButtonEventArgs e)
        {
            try
            {
                if (Node.ConnectedNodes.Length != 1 && config.Display.SimulateMouseEvents == true)
                {
                    MultiViewer.Master.MainWindow main = (MultiViewer.Master.MainWindow)Application.Current.MainWindow;
                    int res_X = displayGrid.Config.Display.Width;
                    int res_Y = displayGrid.Config.Display.Height;
                    xMon = displayGrid.Config.Width;
                    yMon = displayGrid.Config.Height;
                    double cw = ((double)xMon * res_X) / ((int)main.displayGrid.ActualWidth);
                    double ch = ((double)yMon * res_Y) / ((int)main.displayGrid.ActualHeight);
                    try
                    {
                        double Mouse_x = e.MouseDevice.GetPosition(this).X;
                        double Mouse_y = e.MouseDevice.GetPosition(this).Y;
                        for (int j = 0; j < yMon; j++)
                        {
                            for (int i = 0; i < xMon; i++)
                            {
                                if ((Mouse_x >= i * main.displayGrid.ActualWidth / xMon) &&
                                    (Mouse_x <= (i + 1) * main.displayGrid.ActualWidth / xMon) &&
                                    (Mouse_y >= j * main.displayGrid.ActualHeight / yMon) &&
                                    (Mouse_y <= (j + 1) * main.displayGrid.ActualHeight / yMon))
                                {
                                    //Show cursor in the left upper window
                                    int[] nodes = new int[1];
                                    nodes[0] = Node.ClientNodes[xMon * j + i];
                                    Node.Notify(nodes, new MouseClickNotification { Button = MultiViewer.Display.MouseButton.RightDown, Position = new Point(Mouse_x * cw - (res_X * i), Mouse_y * ch - (res_Y * j)) });
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }
            }
            catch (Exception exc)
            {
                MessageBox.Show("Cannot load mouse configuration: " + exc.Message);
                Close();
                return;
            }
            base.OnPreviewMouseRightButtonDown(e);
        }

        protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
        {
            if (ActualWidth <= 0 || ActualHeight <= 0)
                return;
            Point mousePos = e.GetPosition(this);
            int col = (int)(config.Width * mousePos.X / ActualWidth);
            if (col >= config.Width)
                col = config.Width - 1;
            int row = (int)(config.Height * mousePos.Y / ActualHeight);
            if (row >= config.Height)
                row = config.Height - 1;
            Debug.WriteLine(String.Format("MouseWheel: delta = {0}; node = ({1},{2})",e.Delta,col,row));
            Node.Notify(new int[] { 1 + row + col * config.Width },
                new MouseWheelNotification
                {
                    Delta = e.Delta,
                    X = mousePos.X / ActualWidth,
                    Y = mousePos.Y / ActualHeight
                });
            base.OnPreviewMouseWheel(e);
        }

        protected override void OnNodeConnected()
        {
            base.OnNodeConnected();
            // Load configuration
            try
            {
                config = (MasterConfigurationSection)ConfigurationManager.GetSection("defaultDisplayGrid");
            }
            catch (Exception exc)
            {
                MessageBox.Show("Cannot load display grid configuration: " + exc.Message);
                Close();
                return;
            }
            displayGrid.Config = config;

            // Subscribe to connection messages
            Node.NodeConnected += OnNodeConnectedAsync;
            Node.NodeDisconnected += OnNodeDisconnectedAsync;

            // Empty handler of InitDisplayRequest message
            Node.AddMessageHandler<InitDisplayRequest>(
                new MessageHandler<InitDisplayRequest>(delegate(InitDisplayRequest dummy) 
                { 
                    return true; 
                }));

            Node.AddTransactionHandler<ShowImage,ShowImageTransactionState>(Dispatcher,
                ShowImagePrepareHandler,
                ShowImageCommitHandler,
                ShowImageRollbackHandler);

            Node.AddTransactionHandler<SetVisibleRect,SetVisibleRectTransactionState>(Dispatcher,
                SetVisibleRectPrepareHandler,
                SetVisibleRectCommitHandler,
                SetVisibleRectRollbackHandler);
        }

        private void ShowImagePrepareHandler(ShowImage request, HandlerResult<ShowImageTransactionState> result)
        {
            result.SetSuccess(new ShowImageTransactionState(request, null));
        }

        private void ShowImageCommitHandler(ShowImageTransactionState state)
        {
            // Nothing to do here
        }

        private void ShowImageRollbackHandler(ShowImageTransactionState state)
        {
            // Nothing to do here
        }

        private void SetVisibleRectPrepareHandler(SetVisibleRect request, HandlerResult<SetVisibleRectTransactionState> result)
        {
            Trace.WriteLine(String.Format("Preparing visible rect {0}",request));
            result.SetSuccess(new SetVisibleRectTransactionState(request, null));
        }

        private void SetVisibleRectCommitHandler(SetVisibleRectTransactionState state)
        {
            Trace.WriteLine(String.Format("Show visible rect {0}",state.Request));
        }

        private void SetVisibleRectRollbackHandler(SetVisibleRectTransactionState state)
        {
            Trace.WriteLine(String.Format("Failed to set visible rect {0}",state.Request));
        }


        private void OnNodeConnectedAsync(int nodeId)
        {
            UpdateConnectionsAsync(new int[] { nodeId }, new int[] { });
        }

        private void OnNodeDisconnectedAsync(int nodeId)
        {
            UpdateConnectionsAsync(new int[] { }, new int[] { nodeId });
        }

        private void UpdateConnectionsAsync(int[] connected, int[] disconnected)
        {
            Dispatcher.BeginInvoke(new SyncInvoker(delegate()
            {
                // Enumerate all newly connected nodes
                foreach (int nodeId in connected)
                    if(nodeId != 0)
                        if(displayGrid[nodeId].NodeState == ClientNodeState.Disconnected)
                            UpdateNodeState(nodeId, ClientNodeState.Initializing);
                // Enumerate all newly disconnected nodes
                foreach (int nodeId in disconnected)
                    if(nodeId != 0)
                        if (displayGrid[nodeId].NodeState != ClientNodeState.Disconnected)
                            UpdateNodeState(nodeId, ClientNodeState.Disconnected);
            }));
        }

        /// <summary>Changes state of given node</summary>
        /// <param name="nodeId">Node id to change state</param>
        /// <param name="state">New state</param>
        private void UpdateNodeState(int nodeId, ClientNodeState state)
        {
            displayGrid[nodeId].NodeState = state;
            if (state == ClientNodeState.Initializing)
                if (Node.ConnectedNodes.Contains(nodeId))
                    BeginSend<InitDisplayRequest>(
                        new int[] { nodeId },
                        new InitDisplayRequest(config),
                        InitDisplayResponseHandler);
                else
                    displayGrid[nodeId].NodeState = ClientNodeState.Disconnected;

            
            if(displayGrid.AllNodesAreConnected)
                ThreadPool.QueueUserWorkItem(
                    dummy => Node.Perform(new ShowImage { Image = "@VirtualEarth" }));
        }

        private void InitDisplayResponseHandler(int[] nodes, Response[] responses)
        {
            if (responses[0].IsFault)
                Trace.WriteLine("Failed to init display on node " + nodes[0] + ": " + responses[0].Fault.Message);
            else if (responses[0].Success == null ||
                !(responses[0].Success is bool) ||
                !((bool)responses[0].Success))
                Trace.WriteLine("Node " + nodes[0] + " is responded with wrong value");
            else
            {
                UpdateNodeState(nodes[0], ClientNodeState.Connected);
                return;
            }
            // Retry in some time                
            const int RetryDelay = 1000;
            ThreadPool.QueueUserWorkItem(dummy =>
            {
                Thread.Sleep(RetryDelay);
                Dispatcher.BeginInvoke(new SyncInvoker(delegate()
                {
                    UpdateNodeState(nodes[0], ClientNodeState.Initializing);
                }));
            });
        }

        protected override void OnClosed(EventArgs e)
        {
            // Notify all nodes about shutdown
            Node.Send(new ShutdownRequest());
            base.OnClosed(e);
        }
    }  
}
